Index: includes/bootstrap.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v
retrieving revision 1.222
diff -u -p -r1.222 bootstrap.inc
--- includes/bootstrap.inc	6 Sep 2008 15:20:09 -0000	1.222
+++ includes/bootstrap.inc	6 Sep 2008 19:04:26 -0000
@@ -907,7 +907,7 @@ function drupal_get_messages($type = NUL
  * @return bool
  *   TRUE if access is denied, FALSE if access is allowed.
  */
-function drupal_is_denied($ip) {
+function drupal_denied_ip($ip) {
   // Because this function is called on every page request, we first check
   // for an array of IP addresses in settings.php before querying the
   // database.
@@ -922,6 +922,34 @@ function drupal_is_denied($ip) {
 }
 
 /**
+ * Check to see if a referrer has been blocked.
+ *
+ * Blocked referrers are stored in the database by default. However for
+ * performance reasons we allow an override in settings.php. This allows us
+ * to avoid querying the database at this critical stage of the bootstrap if
+ * an administrative interface for referrer blocking is not required.
+ *
+ * @param $referrer string
+ *   Referrer to check.
+ * @return bool
+ *   TRUE if access is denied, FALSE if access is allowed.
+ */
+
+function drupal_denied_referrer($referrer) {
+  // Because this function is called on every page request, we first check
+  // for an array of IP addresses in settings.php before querying the
+  // database.
+  $blocked_referrers = variable_get('blocked_referrers', NULL);
+  if (isset($blocked_referrers) && is_array($blocked_referrers)) {
+    return in_array($referrer, $blocked_referrers);
+  }
+  else {
+    $sql = "SELECT 1 FROM {blocked_referrers} WHERE referrer = '%s'";
+    return (bool) db_result(db_query($sql, $referrer));
+  }
+}
+
+/**
  * Generates a default anonymous $user object.
  *
  * @return Object - the user object.
@@ -1018,12 +1046,19 @@ function _drupal_bootstrap($phase) {
       break;
 
     case DRUPAL_BOOTSTRAP_ACCESS:
-      // Deny access to blocked IP addresses - t() is not yet available.
-      if (drupal_is_denied(ip_address())) {
+      // Deny access to blocked IP addresses and referrers - t() is not yet available.
+      if (drupal_denied_ip(ip_address())) {
         header('HTTP/1.1 403 Forbidden');
         print 'Sorry, ' . check_plain(ip_address()) . ' has been banned.';
         exit();
       }
+      // Apache refers to referrer as referer [sic].
+      elseif (drupal_denied_referrer(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST))) {
+        header('HTTP/1.0 403 Forbidden');
+        print "Sorry, your referrer has been banned.";
+        exit();
+      }
+
       break;
 
     case DRUPAL_BOOTSTRAP_SESSION:
Index: modules/statistics/statistics.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/statistics/statistics.admin.inc,v
retrieving revision 1.10
diff -u -p -r1.10 statistics.admin.inc
--- modules/statistics/statistics.admin.inc	6 Sep 2008 08:36:21 -0000	1.10
+++ modules/statistics/statistics.admin.inc	6 Sep 2008 19:04:26 -0000
@@ -115,6 +115,7 @@ function statistics_top_referrers() {
     array('data' => t('Hits'), 'field' => 'hits', 'sort' => 'desc'),
     array('data' => t('Url'), 'field' => 'url'),
     array('data' => t('Last visit'), 'field' => 'last'),
+    array('data' => t('Ban referrer')),
   );
 
   $query .= tablesort_sql($header);
@@ -122,11 +123,12 @@ function statistics_top_referrers() {
 
   $rows = array();
   while ($referrer = db_fetch_object($result)) {
-    $rows[] = array($referrer->hits, _statistics_link($referrer->url), t('@time ago', array('@time' => format_interval($_SERVER['REQUEST_TIME'] - $referrer->last))));
+    $referrer_parsed = parse_url($referrer->url, PHP_URL_HOST);
+    $rows[] = array($referrer->hits, _statistics_link($referrer->url), t('@time ago', array('@time' => format_interval($_SERVER['REQUEST_TIME'] - $referrer->last))), l(t("Ban Referrer"), "admin/settings/referrer-blocking/" . check_url($referrer_parsed)));
   }
 
   if (empty($rows)) {
-    $rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 3));
+    $rows[] = array(array('data' => t('No statistics available.'), 'colspan' => 4));
   }
 
   $output = theme('table', $header, $rows);
Index: modules/system/system.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.admin.inc,v
retrieving revision 1.87
diff -u -p -r1.87 system.admin.inc
--- modules/system/system.admin.inc	6 Sep 2008 08:36:21 -0000	1.87
+++ modules/system/system.admin.inc	6 Sep 2008 19:04:26 -0000
@@ -1146,8 +1146,6 @@ function system_ip_blocking_form($form_s
     '#type' => 'submit',
     '#value' => t('Save'),
   );
-  $form['#submit'][] = 'system_ip_blocking_form_submit';
-  $form['#validate'][] = 'system_ip_blocking_form_validate';
   return $form;
 }
 
@@ -1197,6 +1195,93 @@ function system_ip_blocking_delete_submi
 }
 
 /**
+ * Menu callback. Display blocked referrers.
+ */
+function system_referrer_blocking() {
+  $output = '';
+  $rows = array();
+  $header = array(t('Referrer'), t('Operations'));
+  $result = db_query('SELECT * FROM {blocked_referrers}');
+  while ($referrer = db_fetch_object($result)) {
+    $rows[] = array(
+      $referrer->referrer,
+      l(t('delete'), "admin/settings/referrer-blocking/delete/$referrer->rid"),
+    );
+  }
+
+  $output .= drupal_get_form('system_referrer_blocking_form');
+
+  $output .= theme('table', $header, $rows);
+
+  return $output;
+}
+
+/**
+ * Define the form for blocking referrers.
+ *
+ * @ingroup forms
+ * @see system_referrer_blocking_form_validate()
+ * @see system_referrer_blocking_form_submit()
+ */
+function system_referrer_blocking_form($form_state) {
+  $form['referrer'] = array(
+    '#title' => t('Referrer'),
+    '#type' => 'textfield',
+    '#size' => 64,
+    '#maxlength' => 255,
+    '#default_value' => arg(3),
+    '#description' => t('Enter a valid Referrer.'),
+  );
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Save'),
+  );
+  return $form;
+}
+
+function system_referrer_blocking_form_validate($form, &$form_state) {
+  $referrer = trim($form_state['values']['referrer']);
+  if (db_result(db_query("SELECT * FROM {blocked_referrers} WHERE referrer = '%s'", $referrer))) {
+    form_set_error('referrer', t('This referrer is already blocked.'));
+  }
+  else if ($referrer == $_SERVER['HTTP_HOST']) {
+    form_set_error('referrer', t('You may not block your own host.'));
+  }
+}
+
+function system_referrer_blocking_form_submit($form, &$form_state) {
+  $referrer = trim($form_state['values']['referrer']);
+  db_query("INSERT INTO {blocked_referrers} (referrer) VALUES ('%s')", $referrer);
+  drupal_set_message(t('The referrer %referrer has been blocked.', array('%referrer' => $referrer)));
+  $form_state['redirect'] = 'admin/settings/referrer-blocking';
+  return;
+}
+
+/**
+ * Referrer deletion confirm page.
+ *
+ * @see system_referrer_blocking_delete_submit()
+ */
+function system_referrer_blocking_delete(&$form_state, $rid) {
+  $form['blocked_referrer'] = array(
+    '#type' => 'value',
+    '#value' => $rid,
+  );
+  return confirm_form($form, t('Are you sure you want to delete %referrer?', array('%referrer' => $rid['referrer'])), 'admin/settings/referrer-blocking', t('This action cannot be undone.'), t('Delete'), t('Cancel'));
+}
+
+/**
+ * Process system_referrer_blocking_delete form submissions.
+ */
+function system_referrer_blocking_delete_submit($form, &$form_state) {
+  $blocked_referrer = $form_state['values']['blocked_referrer'];
+  db_query("DELETE FROM {blocked_referrers} WHERE rid = %d", $blocked_referrer['rid']);
+  watchdog('user', 'Deleted %referrer', array('%referrer' => $blocked_referrer['referrer']));
+  drupal_set_message(t('The referrer address %referrer was deleted.', array('%referrer' => $blocked_referrer['referrer'])));
+  $form_state['redirect'] = 'admin/settings/referrer-blocking';
+}
+
+/**
  * Form builder; The general site information form.
  *
  * @ingroup forms
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.263
diff -u -p -r1.263 system.install
--- modules/system/system.install	6 Sep 2008 08:36:21 -0000	1.263
+++ modules/system/system.install	6 Sep 2008 19:04:27 -0000
@@ -568,6 +568,29 @@ function system_schema() {
     ),
     'primary key' => array('iid'),
   );
+
+  $schema['blocked_referrers'] = array(
+    'description' => t('Stores blocked referrers.'),
+    'fields' => array(
+       'rid' => array(
+        'description' => t('Primary Key: unique ID for referrers.'),
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'referrer' => array(
+        'description' => t('Referrer'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+    ),
+    'indexes' => array(
+      'blocked_referrer' => array('referrer'),
+    ),
+    'primary key' => array('rid'),
+  );
 
   $schema['cache'] = array(
     'description' => t('Generic cache table for caching things not separated out into their own tables. Contributed modules may also use this to store cached items.'),
@@ -3049,6 +3072,40 @@ function system_update_7010() {
 }
 
 /**
+ * Add the ability to perform blocking of traffic based on referrer.
+ */
+function system_update_7011() {
+  $ret = array();
+
+  $schema['blocked_referrers'] = array(
+    'description' => t('Stores blocked referrers.'),
+    'fields' => array(
+       'rid' => array(
+        'description' => t('Primary Key: unique ID for referrers.'),
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'referrer' => array(
+        'description' => t('Referrer'),
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+    ),
+    'indexes' => array(
+      'blocked_referrer' => array('referrer'),
+    ),
+    'primary key' => array('rid'),
+  );
+
+  db_create_table($ret, 'blocked_referrers', $schema['blocked_referrers']);
+
+  return $ret;
+}
+
+/**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.617
diff -u -p -r1.617 system.module
--- modules/system/system.module	6 Sep 2008 08:36:21 -0000	1.617
+++ modules/system/system.module	6 Sep 2008 19:04:27 -0000
@@ -171,7 +171,7 @@ function system_perm() {
     'access site reports' => t('View reports from system logs and other status information.'),
     'select different theme' => t('Select a theme other than the default theme set by the site administrator.'),
     'administer files' => t('Manage user-uploaded files.'),
-    'block IP addresses' => t('Block IP addresses from accessing your site.'),
+    'block traffic' => t('Block IP addresses and/or referrers from accessing your site.'),
   );
 }
 
@@ -503,22 +503,44 @@ function system_menu() {
     'title' => 'IP address blocking',
     'description' => 'Manage blocked IP addresses.',
     'page callback' => 'system_ip_blocking',
-    'access arguments' => array('block IP addresses'),
+    'access arguments' => array('block traffic'),
   );
   $items['admin/settings/ip-blocking/%'] = array(
     'title' => 'IP address blocking',
     'description' => 'Manage blocked IP addresses.',
     'page callback' => 'system_ip_blocking',
-    'access arguments' => array('block IP addresses'),
+    'access arguments' => array('block traffic'),
     'type' => MENU_CALLBACK,
   );
   $items['admin/settings/ip-blocking/delete/%blocked_ip'] = array(
     'title' => 'Delete IP address',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('system_ip_blocking_delete', 4),
-    'access arguments' => array('block IP addresses'),
+    'access arguments' => array('block traffic'),
     'type' => MENU_CALLBACK,
   );
+
+  // Referrer blocking.
+  $items['admin/settings/referrer-blocking'] = array(
+    'title' => 'Referrer blocking',
+    'description' => 'Manage blocked referrers.',
+    'page callback' => 'system_referrer_blocking',
+    'access arguments' => array('block traffic'),
+  );
+  $items['admin/settings/referrer-blocking/%'] = array(
+    'title' => 'Referrer blocking',
+    'description' => 'Manage blocked referrers.',
+    'page callback' => 'system_referrer_blocking',
+    'access arguments' => array('block traffic'),
+    'type' => MENU_CALLBACK,
+  );
+  $items['admin/settings/referrer-blocking/delete/%blocked_referrer'] = array(
+    'title' => 'Delete referrer',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('system_referrer_blocking_delete', 4),
+    'access arguments' => array('block traffic'),
+    'type' => MENU_CALLBACK,
+  );
 
   // Settings:
   $items['admin/settings/site-information'] = array(
@@ -671,6 +693,21 @@ function blocked_ip_load($iid) {
 }
 
 /**
+ * Retrieve a blocked referrer from the database.
+ *
+ * @param $rid integer
+ *   The ID of the blocked referrer to retrieve.
+ *
+ * @return
+ *   The blocked referrer from the database as an array.
+ */
+function blocked_referrer_load($rid) {
+  $blocked_referrer = db_fetch_array(db_query("SELECT * FROM {blocked_referrers} WHERE rid = %d", $rid));
+  return $blocked_referrer;
+}
+
+
+/**
  * Menu item access callback - only admin or enabled themes can be accessed.
  */
 function _system_themes_access($theme) {
@@ -2019,11 +2056,23 @@ function system_goto_action($object, $co
  */
 function system_block_ip_action() {
   $ip = ip_address();
-  db_query("INSERT INTO {blocked_ips} (ip) VALUES ('%s')", $ip);;
+  db_query("INSERT INTO {blocked_ips} (ip) VALUES ('%s')", $ip);
   watchdog('action', 'Banned IP address %ip', array('%ip' => $ip));
 }
 
 /**
+ * Implementation of a Drupal action.
+ * Blocks the user's referrer.
+ */
+function system_block_referrer_action() {
+  // Apache spells referrer, referer.
+  $referrer = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST);
+  db_query("INSERT INTO {blocked_referrers} (referrer) VALUES ('%s')", $referrer);
+  watchdog('action', 'Banned IP address %referrer', array('%referrer' => $referrer));
+}
+
+
+/**
  * Generate an array of time zones and their local time&date.
  */
 function _system_zonelist() {
